local skynet = require "skynet"
local socket = require "socket"
local pack_helper = require "pack_helper"
local cjson = require "cjson"
local netpack = require "netpack"
local parser = require "parser"
local protobuf = require "protobuf"
-- local Message = require ".robotsvr.Message"

local Network = class("Network")

function Network:ctor(ip,port)
    self.fd = nil
    self.lastPackage = ""
    self.service = nil
    self.tbProtoId = {}
    self.protobuf = nil
    self.tbSelfMsg = {}
    -- self.msg = Message.new()
    self.sid = 0
    self.ext = 0 --备用
    self.maxSid = 50000
    --加载proto文件
    self:loadProto()
    self:open(ip,port)
end

---------------------------------------------------------
-- private
---------------------------------------------------------
--取pid
function Network:readPid( text ) --big endian
    return text:byte(5) * 256 + text:byte(6) 
end

function Network:readExt( text ) --big endian
    return text:byte(3) * 256 + text:byte(4) 
end

function Network:readShortLittle( text ) --little endian
    return text:byte(1) + text:byte(2) * 256
end

--取大端2个字节
function Network:readShortBig( text ) --big endian
    return text:byte(1)*256 + text:byte(2)
end

--解包
function Network:unpackPackage(text)
    local size = #text
    if size < 2 then
        return nil, text
    end
    local pack_size = self:readShortBig(text)
    if size < pack_size+2 then
        return nil, text
    end
    local offset = 2
    local msg = text:sub(offset + 1, offset+pack_size)
    local last = text:sub(offset + 1 + pack_size)
    return msg, last
end

--解消息
function Network:decodeMsg( msgbuf )
    local size = #msgbuf
    if size<4 then return false end
    -- msgbuf = self:decryptData(msgbuf)

    local sid = self:readShortBig(msgbuf)
    local ext = self:readExt(msgbuf)
    local pack_id = self:readPid(msgbuf)
    local pack_name = pack_helper.get_pack(pack_id)
    if not pack_name then
        print('decodeMsg no found pack_id:'..pack_id)
        return
    end
    local  pack_body = msgbuf:sub(5)

    local ok, pack_body = pcall(self.protobuf.decode, pack_name, pack_body, #pack_body)
    if not ok then
        print('decodeMsg msgbody invalid package', pack_body)
    end

    return pack_name, pack_body
end


--加载proto文件
function Network:loadProto()
    local proto_file = skynet.getenv("proto_config")
    --print("######P",proto_file)
    --local pbc_env = pbc.init(proto_file, {'game',})

    --pbc.load(proto_file..'game/niuniu_pack.proto', proto_file..'game/')
    local function load( protofile , protopath)
        local ok, result = x_pcall(parser.register, protofile, protopath)
        if not ok then
            print("error###############protobuff load:", protofile,protopath,result)
            --return nil
        end 
        for pack_name,field in pairs(parser.cache_fields) do
            local pack_id = pack_helper.get_pack(pack_name)
            if pack_id then
                for msg_name,msg_id in pairs(field) do
                    self.tbProtoId[msg_name] = {pack_name,pack_id}
                end
            end
        end     
    end
    local function loadPath( path, dirs )
        if not dirs then dirs = {''} end
        for k,dir in ipairs(dirs) do
            local dir_path = path..dir
            local file_list = dir_list(dir_path)
            load(file_list, dir_path)
        end 
    end
    loadPath(proto_file,{'base','gate','game','robot'})
    self.protobuf = protobuf
end

--组装json包
function Network:encodeJsonMsg(pack_id, msg_body)
    local msgbody
    if next(msg_body) then
        msgbody = cjson.encode(msg_body)
        if not msgbody then
            log.error('encode json msg body failed')
            return
        end
    else
        msgbody = ''
    end
    --http://cloudwu.github.io/lua53doc/manual.html#6.4.2
    local msg = string.pack(">H", pack_id)..msgbody
    if msg == nil then
        log.error('msg pack failed'..pack_id)
        return
    end
    return msg
end

--连接socket
function Network:open(ip, port)
    self.fd = socket.open(ip, port)
    if not self.fd then
        print("连接 "..ip..":"..port.."失败")
        return
    end
    skynet.fork(function() self:recvLoop() end)
    print("########连接"..ip..":"..port.."成功#########")
end

function Network:close()
    print("____________Network close__________")
    socket.close(self.fd)
    self.fd = nil
    self.lastPackage = ""
    self:dispatchMessage("socket_close")
    -- skynet.exit()
end

function Network:redirect(ip, port)
    --local handler = self.handler
    self:close()
    self:open(ip, port)
    if not self.fd then
        print("连接 "..ip..":"..port.."失败")
        return false
    end
    --self.handler = handler
    print("连接"..ip..":"..port.."成功")
    return true
end

--接收消息
function Network:recvLoop()
    while true do
        --print(">>>M:recv_loop>>>")
        if not self.fd then
            --print("接收数据协程结束")
            return
        end
        local str = socket.read(self.fd)
        if str then
            self:receive(str)
        else
            --print("接收数据协程结束")
            self:close()
            return
        end
    end
end

function Network:receive(str)
    -- print("收到数据"..#str)
    self.lastPackage = self.lastPackage .. str
    local msg
    while #self.lastPackage > 0 do
        msg, self.lastPackage = self:unpackPackage(self.lastPackage)
        if not msg then break end
        local ok , pack_name, pack_body = pcall(handler(self,self.decodeMsg), msg)        
        if not ok then
            print("message dispatch error: "..tostring(pack_name))
        else
           --print("receive msg ___",pack_body)
           for k,v in pairs(pack_body) do 
               self:dispatchMessage(k,v)               
           end
        end
    end
end

--派发
function Network:dispatch(pack_name, pack_body)
    if pack_name and pack_body then
        for msgname, msgbody in pairs(pack_body) do
            --print(">>>>M:dispatch_msg>>>>>", msgname, msgbody)
            --skynet.send(self.service, "lua", "network", msgname, msgbody)
        end
    end
end

--socket写入
function Network:writePackage(msg)
    if not self.fd or not msg then
        print("##########writePackage error######")
        return false
    end
    local tmpmsg, sz = netpack.pack(msg)
    local ok,err = pcall(socket.write, self.fd, tmpmsg, sz)
    if not ok then
        print('error#########send_msgto_remote faild:',err,self.fd,msg)       
        return false
    end
    return true
end

--组装消息
function Network:encodeMsg(pack_name, pack_id, msg_body)
    local msgbody
    if next(msg_body) then
        local status
        status, msgbody = pcall(self.protobuf.encode, pack_name, msg_body)
        if not status then
            print('___encode msg body failed',msgbody)
            return
        end
    else
        msgbody = ''
    end
    self.sid = self.sid + 1    
    if self.sid > self.maxSid then 
        self.sid = 1
    end
    local msg = string.pack(">H", self.sid)..string.pack(">H", self.ext)..string.pack(">H", pack_id)..msgbody
    if msg == nil then
        print('#######msg pack failed'..pack_id)
        return
    end    
    return msg
end

function Network:encryptData(content,key)
    key = key or "meili";
    local function __getBytes(...)
        return {...}
    end
    local __content_bytes = __getBytes(string.byte(content,1,-1));
    local __key_bytes = __getBytes(string.byte(key,1,-1));
    local __pos = 1;
    for k,v in pairs(__content_bytes) do
        if __pos > #__key_bytes then
            __pos = 1;
        end
        __content_bytes[k] = ((v + 7)%256) ~ __key_bytes[__pos]
        __pos = __pos + 1
    end
    return string.char(table.unpack(__content_bytes));
end

function Network:decryptData(content,key)
    key = key or "meili";
    local function __getBytes(...)
        return {...}
    end
    local __content_bytes = __getBytes(string.byte(content,1,-1));
    local __key_bytes = __getBytes(string.byte(key,1,-1));
    local __pos = 1;
    for k,v in pairs(__content_bytes) do
        if __pos > #__key_bytes then
            __pos = 1;
        end
        __content_bytes[k] = ((v ~ __key_bytes[__pos]) - 7 )%256
        __pos = __pos + 1
    end
    return string.char(table.unpack(__content_bytes));
end
---------------------------------------------------------
-- public
---------------------------------------------------------
function Network:setSid(sid)
    self.sid = sid
end

--发送
function Network:send(data)
    for k,v in pairs(data) do 
        local pack =  self.tbProtoId[k]
        if not pack then
            print('send message faild no found msg_name:',msg_name,k)
            return
        end
        local msg = self:encodeMsg(pack[1], pack[2], data)
        -- msg = self:encryptData(msg)
        if msg then
            return self:writePackage(msg)
        else
            print('write_package faild :',k)
        end
    end
end

--发送josn
function Network:sendJson(data)
    for k,v in pairs(data) do 
        local pack =  10000
        if not pack then
            print('send message faild no found msg_name:',msg_name,k)
            return
        end
        local msg = self:encodeJsonMsg(pack, v)
        print("___",msg)
        -- msg = self:encryptData(msg)
        if msg then
            return self:writePackage(msg)
        else
            print('write_package faild :',k)
        end
    end    
end

--注册消息回调
function Network:register(name,callback)
    -- self.msg:registerSelf(name,callback)
    self.tbSelfMsg[name] = callback
end

--消息派发
function Network:dispatchMessage(cmd, ... )
    local func = self.tbSelfMsg[cmd]
    if not func then 
        --log.error("####### cmd "..cmd .." not found at robot "..self.svrName)
        print("########### Network dispatchMessage not found message func ",cmd)
        return
    end
    --skynet.retpack(xx_pcall(func, ...))
    func(...)
end


return Network
